﻿#region Copyright
// Copyright (c) 2011-2012 RaisingForce Team (Alikin Sergey)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Contracts;
using System.Windows;
using System.Windows.Threading;
using Microsoft.Win32;
using RaisingForce.Raise.Hardware;
using RaisingForce.Raise.Measures;
using RaisingForce.Raise.Presentation;
using RaisingForce.Raise.Presentation.Controls;
using RaisingForce.Raise.Presentation.Names;
using RaisingForce.Raise.Presentation.Rules;
using RaisingForce.Raise.Presentation.ViewModel;
using RaisingForce.Raise.Presentation.ViewModel.Extensions;
using RaisingForce.Raise.Presentation.ViewModel.Specialized;
using RaisingForce.RaiseHardwareTest.Hardware;
using RaisingForce.RaiseHardwareTest.Properties;
using RaisingForce.RaiseHardwareTest.ViewModel.MainStates;

namespace RaisingForce.RaiseHardwareTest.ViewModel.Extensions
{
    internal static class MainViewModelExtensions
    {
        #region Basic Extension Methods

        public static string GetHeaderString(this MainViewModel viewModel)
        {
            Contract.Requires(viewModel != null);
            Contract.Ensures(Contract.Result<string>() != null);
            var selectedHardware = viewModel.HardwareSelector.SelectedHardware;
            string selectedHardwarePostfix =
                selectedHardware != null
                && !viewModel.IsDisconnected
                && !viewModel.IsConnecting ?
                string.Format(" - {0}", selectedHardware.Name)
                : string.Empty;
            return viewModel.DisplayName + selectedHardwarePostfix;
        }

        public static void ImportOptions(
            this MainViewModel viewModel,
            string filename)
        {
            Contract.Requires(viewModel != null);
            if (!viewModel.IsDisconnected)
            {
                throw new InvalidOperationException(
                    "Cannot import options, while not in Disconnected state.");
            }
            viewModel.Options = HardwareOptionsHelper.Load(filename);
            viewModel.ResetHardware();
        }

        public static void ExportOptions(
            this MainViewModel viewModel,
            string filename)
        {
            Contract.Requires(viewModel != null);
            HardwareOptionsHelper.Save(viewModel.Options, filename);
        }

        #endregion

        #region CreateCommands Methods

        public delegate ObservableCollection<SettingViewModel> CreateInputControlsFunc(
            SettingsViewModel viewModel);

        public delegate ObservableCollection<CommandViewModel> CreateSettingsCommandsFunc(
            SettingsViewModel viewModel);

        public static ObservableCollection<CommandViewModel> CreateCommands(
            this MainViewModel viewModel,
            Window ownerWindow,
            CreateInputControlsFunc createInputControls,
            CreateSettingsCommandsFunc createSettingsCommands,
            int measureSelectorsMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(ownerWindow != null);
            Contract.Requires(createInputControls != null);
            Contract.Requires(createSettingsCommands != null);
            Contract.Requires(measureSelectorsMinWidth >= 0);
            Contract.Ensures(Contract.Result<ObservableCollection<CommandViewModel>>() != null);
            return new ObservableCollection<CommandViewModel>(
                new CommandViewModel[]
                {
                    viewModel.CreateConnectCommand(Resources.Command_Name_Connect),
                    viewModel.CreateDisconnectCommand(Resources.Command_Name_Disconnect),
                    viewModel.CreateSettingsCommand(
                        Resources.Command_Name_Settings,
                        Resources.Settings_Title, 
                        ownerWindow,
                        createInputControls,
                        createSettingsCommands),
                    viewModel.CreateMeasuresCommand(
                        Resources.Command_Name_Measures,
                        Resources.Measures_Title,
                        Resources.Measures_DefaultsConfirmation,
                        measureSelectorsMinWidth)
                });
        }

        public static ObservableCollection<LabeledCommandViewModel> CreateLabeledCommands(
            this MainViewModel viewModel,
            int labelMinWidth = 0,
            int moveForwardMinWidth = 0,
            int rotateMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Requires(moveForwardMinWidth >= 0);
            Contract.Requires(rotateMinWidth >= 0);
            Contract.Ensures(Contract.Result<ObservableCollection<LabeledCommandViewModel>>() != null);
            return new ObservableCollection<LabeledCommandViewModel>(
                new LabeledCommandViewModel[]
                {
                    viewModel.CreateStopCommand(
                        Resources.LabeledCommand_Name_Stop,
                        Resources.LabeledCommand_Description_Stop,
                        labelMinWidth),
                    viewModel.CreateMoveCommand(
                        Resources.LabeledCommand_Name_Move,
                        Resources.LabeledCommand_Description_Move,
                        labelMinWidth,
                        moveForwardMinWidth),
                    viewModel.CreateRotateCommand(
                        Resources.LabeledCommand_Name_Rotate,
                        Resources.LabeledCommand_Description_Rotate,
                        labelMinWidth,
                        rotateMinWidth),
                    viewModel.CreateDetectObstacleCommand(
                        Resources.LabeledCommand_Name_DetectObstacle,
                        Resources.LabeledCommand_Description_DetectObstacle,
                        labelMinWidth),
                    viewModel.CreateWheelRunDistanceCommand(
                        Resources.LabeledCommand_Name_WheelRunDistance,
                        Resources.LabeledCommand_Description_WheelRunDistance,
                        labelMinWidth),
                    viewModel.CreateFullTurnTachoCommand(
                        Resources.LabeledCommand_Name_FullTurnTacho,
                        Resources.LabeledCommand_Description_FullTurnTacho,
                        labelMinWidth)
                });
        }

        #endregion

        #region Connect Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateConnectCommand(
            this MainViewModel viewModel,
            string displayName)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p => viewModel.ConnectHardware(),
                    canExecute: p =>
                        viewModel.IsDisconnected
                        && viewModel.PluginedHardwareFound));
        }

        #endregion

        #region Disconnect Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateDisconnectCommand(
            this MainViewModel viewModel,
            string displayName)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p => viewModel.DisconnectHardware(),
                    canExecute: p => viewModel.IsConnected));
        }

        #endregion

        #region Settings Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateSettingsCommand(
            this MainViewModel viewModel,
            string displayName,
            string settingsDisplayName,
            Window ownerWindow,
            CreateInputControlsFunc createInputControls,
            CreateSettingsCommandsFunc createSettingsCommands)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(settingsDisplayName != null);
            Contract.Requires(ownerWindow != null);
            Contract.Requires(createInputControls != null);
            Contract.Requires(createSettingsCommands != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p => ShowSettingsDialog(
                        viewModel,
                        settingsDisplayName,
                        ownerWindow,
                        createInputControls,
                        createSettingsCommands),
                    canExecute: p => viewModel.IsDisconnected));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static void ShowSettingsDialog(
            MainViewModel viewModel,
            string displayName,
            Window ownerWindow,
            CreateInputControlsFunc createInputControls,
            CreateSettingsCommandsFunc createSettingsCommands)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(ownerWindow != null);
            Contract.Requires(createInputControls != null);
            Contract.Requires(createSettingsCommands != null);
            // Create Settings view model
            var settings = new SettingsViewModel(
                displayName,
                viewModel.Options,
                viewModel.UsingMeasures,
                Resources.Measures_ShortName_Tacho);
            settings.InputControls = createInputControls(settings);
            // Create Dialog view model for Settings
            var dialog = new DialogViewModel(
                settings,
                okExecute: d =>
                {
                    if (!viewModel.Options.Equals(settings.Options))
                    {
                        viewModel.ResetHardware();
                        viewModel.Options = settings.Options;
                    }
                },
                okCanExecute: d => settings.IsValid,
                okExecutionFailed: d =>
                    d.ShowMessage(settings.GetErrors(), Resources.ErrorTitle_InputData));
            dialog.Commands = createSettingsCommands(settings);
            // Create size support provider for settings window           
            WindowSupportProvider windowSupportProvider =
                new WindowSupportProvider(
                    Settings.Default,
                    "SettingsWindow_Height",
                    "SettingsWindow_Width",
                    "SettingsWindow_IsMaximized");
            // Show Dialog in ModelWindow
            ModelWindow.Show(
                dialog,
                ModelWindowShowModeValues.Dialog,
                owner: ownerWindow,
                width: Settings.Default.SettingsWindow_Width,
                height: Settings.Default.SettingsWindow_Height,
                startupLocation: WindowStartupLocation.CenterOwner,
                showInTaskbar: false,
                prologAction: window =>
                {
                    windowSupportProvider.AddSupportForWindow(window);
                    window.Closed += (s, ev) =>
                    {
                        windowSupportProvider.SaveWindowSize();
                        windowSupportProvider.RemoveSupportForWindow();
                    };
                    window.Icon = ApplicationHelper.Icon;
                });
        }

        #endregion

        #region Measures Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateMeasuresCommand(
            this MainViewModel viewModel,
            string displayName,
            string dialogCaption,
            string dialogDefaultsConfirmation,
            int selectorsMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(MessageHelper.MessageIsValid(dialogCaption));
            Contract.Requires(MessageHelper.MessageIsValid(dialogDefaultsConfirmation));
            Contract.Requires(selectorsMinWidth >= 0);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(
                    p => ShowMeasureSettingsDialog(
                        viewModel,
                        dialogCaption,
                        dialogDefaultsConfirmation,
                        selectorsMinWidth)));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static void ShowMeasureSettingsDialog(
            MainViewModel viewModel,
            string caption,
            string defaultsConfirmation,
            int selectorsMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(MessageHelper.MessageIsValid(caption));
            Contract.Requires(MessageHelper.MessageIsValid(defaultsConfirmation));
            Contract.Requires(selectorsMinWidth >= 0);
            WindowViewModel window = viewModel.Window;
            if (window != null)
            {
                var contentViewModel =
                    new SelectMeasuresViewModel(viewModel.UsingMeasures)
                    {
                        SelectorsMinWidth = selectorsMinWidth,
                        DistanceMeasureLabel = Resources.Measures_Distance,
                        TimeMeasureLabel = Resources.Measures_Time,
                        AngleMeasureLabel = Resources.Measures_Angle
                    };
                window.ModalDialog = new ModalDialogViewModel(
                    string.Empty,
                    caption,
                    ModalDialogViewModel.CreateButtons(
                        ModalDialogViewModel.DialogMode.ThreeButton,
                        new string[]
                        {
                            ButtonNames.OK, 
                            ButtonNames.Defaults,
                            ButtonNames.Cancel
                        },
                        () => window.ModalDialogOk(() =>
                        {
                            viewModel.UsingMeasures = contentViewModel.SelectedMeasures;
                        }),
                        () =>
                        {
                            if (MessageHelper.AskConfirmation(defaultsConfirmation, caption))
                            {
                                contentViewModel.SetDefaults();
                            }
                        },
                        window.ModalDialogCancel))
                    {
                        DialogContent = contentViewModel
                    };
                window.ModalDialog.Show();
            }
        }

        #endregion

        #region Move Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static LabeledCommandViewModel CreateMoveCommand(
            this MainViewModel viewModel,
            string displayName,
            string description = null,
            int labelMinWidth = 0,
            int dialogContentMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Requires(dialogContentMinWidth >= 0);
            Contract.Ensures(Contract.Result<LabeledCommandViewModel>() != null);
            return new LabeledCommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p =>
                    {
                        ShowMovementDistanceDialog(
                            viewModel,
                            Resources.Move_Dialog_Title,
                            ApplicationHelper.DistanceValueMaxDigits,
                            (distance, direction) => RobotMove(viewModel, distance, direction),
                            dialogContentMinWidth);
                    },
                    canExecute: p => viewModel.IsConnected))
            {
                Description = description,
                ButtonTextMinWidth = labelMinWidth,
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static void ShowMovementDistanceDialog(
            MainViewModel viewModel,
            string message,
            int valueMaxDigits,
            Action<Distance, MovementDirection> action,
            int contentMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(MessageHelper.MessageIsValid(message));
            Contract.Requires(valueMaxDigits >= 0);
            Contract.Requires(action != null);
            Contract.Requires(contentMinWidth >= 0);
            var window = viewModel.Window;
            if (window != null)
            {
                var settings = new SettingsViewModel(
                    string.Empty,
                    viewModel.Options,
                    viewModel.UsingMeasures,
                    Resources.Measures_ShortName_Tacho);
                Distance distance = RobotSavedData.Instance.MoveForwardDistance
                    ?? viewModel.Options.WheelRunDistance;
                settings.InputControls.Add(
                    CreateMovementDistanceControl(
                        settings,
                        valueMaxDigits,
                        labelMinWidth: 0,
                        getDistance: () => distance,
                        setDistance: d => distance = d));
                Action<MovementDirection> moveAction =
                    direction =>
                    {
                        if (settings.IsValid)
                        {
                            window.ModalDialog.Hide();
                            action(distance, direction);
                        }
                        else
                        {
                            MessageHelper.ShowMessage(
                                settings.GetErrors(),
                                Resources.ErrorTitle_InputData,
                                MessageBoxImage.Warning);
                        }
                    };
                window.ModalDialog = new ModalDialogViewModel(
                    string.Empty,
                    message,
                    ModalDialogViewModel.CreateButtons(
                        ModalDialogViewModel.DialogMode.ThreeButton,
                        new string[]
                        {
                            Resources.Button_MoveForward,
                            Resources.Button_MoveBackward,
                            ButtonNames.Cancel
                        },
                        () => moveAction(MovementDirection.Forward),
                        () => moveAction(MovementDirection.Backward),
                        window.ModalDialogCancel))
                {
                    DialogContent = settings,
                    DialogContentMinWidth = contentMinWidth
                };
                window.ModalDialog.Show();
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateMovementDistanceControl(
            SettingsViewModel viewModel,
            int valueMaxDigits,
            int labelMinWidth,
            Func<Distance> getDistance,
            Action<Distance> setDistance)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(valueMaxDigits >= 0);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Requires(getDistance != null);
            Contract.Requires(setDistance != null);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            return new DoubleMeasureSettingBoxViewModel<DistanceRule, IDistanceMeasure>(
                Resources.Move_Dialog_InvalidDistance,
                Resources.Move_Dialog_EmptyDistance,
                viewModel.UsingMeasures.DistanceMeasure,
                value => setDistance(viewModel.UsingMeasures.GetDistance(value)),
                () => ApplicationHelper.GetRoundedDistanceValue(
                    getDistance().Get(viewModel.UsingMeasures.DistanceMeasure),
                    valueMaxDigits)
                    .ToString())
            {
                LabelText = Resources.Move_Dialog_DistanceLabel,
                LabelMinWidth = labelMinWidth,
                MeasureText = viewModel.UsingMeasures.DistanceMeasureText
            };
        }

        public static void RobotMove(
            MainViewModel viewModel,
            Distance movementDistance,
            MovementDirection movementDirection)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(movementDistance != Distance.Unknown);
            RobotSavedData.Instance.MoveForwardDistance = movementDistance;
            HardwareDispatcher.InvokeAsync(viewModel, () =>
            {
                viewModel.SetState(MainStateKeys.Moving);
                try
                {
                    viewModel.Hardware.Engine
                        .Move(movementDistance, movementDirection);
                }
                finally
                {
                    viewModel.SetDoneState();
                }
            });
        }

        #endregion

        #region Rotate Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static LabeledCommandViewModel CreateRotateCommand(
            this MainViewModel viewModel,
            string displayName,
            string description = null,
            int labelMinWidth = 0,
            int dialogContentMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Requires(dialogContentMinWidth >= 0);
            Contract.Ensures(Contract.Result<LabeledCommandViewModel>() != null);
            return new LabeledCommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p =>
                    {
                        ShowRotationDialog(
                            viewModel,
                            message: Resources.Rotate_Dialog_Title,
                            invalidAngleMessage: Resources.Rotate_Dialog_InvalidAngle,
                            action: (angle, direction) => RobotRotate(viewModel, angle, direction),
                            contentMinWidth: dialogContentMinWidth);
                    },
                    canExecute: p => viewModel.IsConnected))
            {
                Description = description,
                ButtonTextMinWidth = labelMinWidth,
            };
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static void ShowRotationDialog(
            MainViewModel viewModel,
            string message,
            string invalidAngleMessage,
            Action<Angle, RotationDirection> action,
            int contentMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(MessageHelper.MessageIsValid(message));
            Contract.Requires(MessageHelper.MessageIsValid(invalidAngleMessage));
            Contract.Requires(action != null);
            Contract.Requires(contentMinWidth >= 0);
            var window = viewModel.Window;
            if (window != null)
            {
                var settings = new SettingsViewModel(
                    string.Empty,
                    viewModel.Options,
                    viewModel.UsingMeasures,
                    Resources.Measures_ShortName_Tacho);
                RobotAngleData angleData = new RobotAngleData(
                    RobotSavedData.Instance.RotateAngle,
                    RobotSavedData.Instance.RotateDirection);
                settings.InputControls.Add(
                    CreateRotationControl(
                        settings,
                        labelMinWidth: 0,
                        getAngle: () => angleData,
                        setAngle: d => angleData = d));
                window.ModalDialog = new ModalDialogViewModel(
                    string.Empty,
                    message,
                    ModalDialogViewModel.DialogButtons.OkCancel,
                    ModalDialogViewModel.CreateCommands(
                        new Action[]
                        {
                            () => 
                            {
                                if (!angleData.Angle.IsUnknown)
                                {
                                    window.ModalDialog.Hide();
                                    action(angleData.Angle, angleData.RotationDirection);
                                }
                                else
                                {
                                    MessageHelper.ShowMessage(
                                        invalidAngleMessage,
                                        Resources.ErrorTitle_InputData, 
                                        MessageBoxImage.Warning);
                                }
                            },
                            window.ModalDialogCancel
                        }))
                {
                    DialogContent = settings,
                    DialogContentMinWidth = contentMinWidth
                };
                window.ModalDialog.Show();
            }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static SettingViewModel CreateRotationControl(
            SettingsViewModel viewModel,
            int labelMinWidth,
            Func<RobotAngleData> getAngle,
            Action<RobotAngleData> setAngle)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Requires(getAngle != null);
            Contract.Requires(setAngle != null);
            Contract.Ensures(Contract.Result<SettingViewModel>() != null);
            double fullTurnAngleValue = Angle.FullTurn.Get(viewModel.UsingMeasures.AngleMeasure);
            return new SliderSettingViewModel(
                value =>
                {
                    Angle angle = viewModel.UsingMeasures.GetAngle(Math.Abs(value));
                    RotationDirection rotationDirection = value < 0 ?
                        RotationDirection.CounterClockwise :
                        RotationDirection.Clockwise;
                    setAngle(new RobotAngleData(angle, rotationDirection));
                },
                () =>
                {
                    RobotAngleData data = getAngle();
                    double multiplier =
                        data.RotationDirection == RotationDirection.CounterClockwise ?
                        -1 : +1;
                    return multiplier * data.Angle.Get(viewModel.UsingMeasures.AngleMeasure);
                })
            {
                LabelText = Resources.Rotate_Dialog_AngleLabel,
                Minimum = -1 * fullTurnAngleValue,
                Maximum = +1 * fullTurnAngleValue,
                ValueStep = fullTurnAngleValue / 24.0,
                ValueFormat = viewModel.UsingMeasures.GetAngleTemplateString()
                    + viewModel.UsingMeasures.AngleMeasureText
            };
        }

        public static void RobotRotate(
            MainViewModel viewModel,
            Angle rotationAngle,
            RotationDirection rotationDirection)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(!rotationAngle.IsUnknown);
            Contract.Requires(Enum.IsDefined(typeof(RotationDirection), rotationDirection));
            RobotSavedData.Instance.RotateAngle = rotationAngle;
            RobotSavedData.Instance.RotateDirection = rotationDirection;
            HardwareDispatcher.InvokeAsync(viewModel, () =>
            {
                viewModel.SetState(MainStateKeys.Rotating);
                try
                {
                    if (rotationAngle < Angle.FullTurn)
                    {
                        viewModel.Hardware.Engine.Rotate(rotationAngle, rotationDirection);
                    }
                    else
                    {
                        viewModel.Hardware.MeasureFullTurnTime(viewModel.Options, rotationDirection);
                    }
                }
                finally
                {
                    viewModel.SetDoneState();
                }
            });
        }

        #endregion

        #region DetectObstacle Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static LabeledCommandViewModel CreateDetectObstacleCommand(
            this MainViewModel viewModel,
            string displayName,
            string description = null,
            int labelMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<LabeledCommandViewModel>() != null);
            return new LabeledCommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p =>
                    {
                        RobotDetectObstacle(viewModel,
                            obstacleDistance =>
                            {
                                ShowDetectedObstacleDialog(
                                    viewModel,
                                    Resources.DetectObstacle_DetectedTemplate,
                                    Resources.DetectObstacle_NotDetected,
                                    obstacleDistance,
                                    ApplicationHelper.DistanceValueMaxDigits);
                            });
                    },
                    canExecute: p => viewModel.IsConnected))
            {
                Description = description,
                ButtonTextMinWidth = labelMinWidth,
            };
        }

        public static void ShowDetectedObstacleDialog(
            MainViewModel viewModel,
            string messageDetectedTemplate,
            string messageNotDetected,
            Distance obstacleDistanse,
            int valueMaxDigits)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(MessageHelper.TemplateIsValid(messageDetectedTemplate, paramCount: 2));
            Contract.Requires(MessageHelper.MessageIsValid(messageNotDetected));
            Contract.Requires(valueMaxDigits >= 0);
            string message = obstacleDistanse.IsUnknown ?
                messageNotDetected :
                string.Format(
                    messageDetectedTemplate,
                    ApplicationHelper.GetRoundedDistanceValue(
                        obstacleDistanse.Get(viewModel.UsingMeasures.DistanceMeasure),
                        valueMaxDigits),
                    viewModel.UsingMeasures.DistanceMeasureText);
            viewModel.ShowMessage(message, caption: viewModel.DisplayName);
        }

        public static void RobotDetectObstacle(
            MainViewModel viewModel,
            Action<Distance> detectionDoneAction)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(detectionDoneAction != null);
            Dispatcher dispatcher = Dispatcher.CurrentDispatcher;
            HardwareDispatcher.InvokeAsync(viewModel, () =>
            {
                viewModel.SetState(MainStateKeys.Detecting);
                Distance obstacleDistance;
                try
                {
                    obstacleDistance = viewModel.Hardware.Sensor.DetectObstacle();
                }
                finally
                {
                    viewModel.SetDoneState();
                }
                dispatcher.Invoke(detectionDoneAction, obstacleDistance);
            });
        }

        #endregion

        #region WheelRunDistance Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static LabeledCommandViewModel CreateWheelRunDistanceCommand(
            this MainViewModel viewModel,
            string displayName,
            string description = null,
            int labelMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<LabeledCommandViewModel>() != null);
            return new LabeledCommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p => RobotMeasureWheelRunDistance(viewModel),
                    canExecute: p => viewModel.IsConnected))
            {
                Description = description,
                ButtonTextMinWidth = labelMinWidth
            };
        }

        public static void RobotMeasureWheelRunDistance(MainViewModel viewModel)
        {
            Contract.Requires(viewModel != null);
            HardwareDispatcher.InvokeAsync(viewModel, () =>
            {
                viewModel.SetState(MainStateKeys.Moving);
                try
                {
                    viewModel.Hardware.MeasureWheelRunDistance(viewModel.Options);
                }
                finally
                {
                    viewModel.SetDoneState();
                }
            });
        }

        #endregion

        #region FullTurnTacho Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static LabeledCommandViewModel CreateFullTurnTachoCommand(
            this MainViewModel viewModel,
            string displayName,
            string description = null,
            int labelMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<LabeledCommandViewModel>() != null);
            return new LabeledCommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p =>
                    {
                        ShowRotationDirectionDialog(
                            viewModel,
                            message: string.Format(
                                Resources.Main_RotationDirectionDialogTemplate,
                                Environment.NewLine,
                                viewModel.Options.LeftTurnTacho,
                                viewModel.Options.RightTurnTacho),
                            action: rotationDirection => RobotMeasureFullTurnTime(viewModel, rotationDirection));
                    },
                    canExecute: p => viewModel.IsConnected))
            {
                Description = description,
                ButtonTextMinWidth = labelMinWidth
            };
        }

        public static void ShowRotationDirectionDialog(
            MainViewModel viewModel,
            string message,
            Action<RotationDirection> action)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(MessageHelper.MessageIsValid(message));
            Contract.Requires(action != null);
            var window = viewModel.Window;
            if (window != null)
            {
                window.ModalDialog = new ModalDialogViewModel(
                    message,
                    viewModel.DisplayName,
                    ModalDialogViewModel.CreateButtons(
                        ModalDialogViewModel.DialogMode.ThreeButton,
                        new string[] 
                        {
                            Resources.Button_LeftTurn, 
                            Resources.Button_RightTurn, 
                            ButtonNames.Cancel 
                        },
                        () => window.ModalDialogOk(() => action(RotationDirection.CounterClockwise)),
                        () => window.ModalDialogOk(() => action(RotationDirection.Clockwise)),
                        window.ModalDialogCancel));
                window.ModalDialog.Show();
            }
        }

        public static void RobotMeasureFullTurnTime(
            MainViewModel viewModel,
            RotationDirection rotationDirection)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(Enum.IsDefined(typeof(RotationDirection), rotationDirection));
            HardwareDispatcher.InvokeAsync(viewModel, () =>
            {
                viewModel.SetState(MainStateKeys.Rotating);
                try
                {
                    viewModel.Hardware.MeasureFullTurnTime(viewModel.Options, rotationDirection);
                }
                finally
                {
                    viewModel.SetDoneState();
                }
            });
        }

        #endregion

        #region Stop Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static LabeledCommandViewModel CreateStopCommand(
            this MainViewModel viewModel,
            string displayName,
            string description = null,
            int labelMinWidth = 0)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Requires(labelMinWidth >= 0);
            Contract.Ensures(Contract.Result<LabeledCommandViewModel>() != null);
            return new LabeledCommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p =>
                    {
                        RobotStop(viewModel);
                    },
                    canExecute: p => viewModel.IsEngineRunning))
            {
                Description = description,
                ButtonTextMinWidth = labelMinWidth,
            };
        }

        public static void RobotStop(MainViewModel viewModel)
        {
            Contract.Requires(viewModel != null);
            HardwareDispatcher.InvokeAsync(viewModel, () =>
            {
                viewModel.Hardware.Engine.Stop();
            });
        }

        #endregion

        #region ShowOptionsFileDialog Method

        public static bool ShowOptionsFileDialog<TDialog>(
            string title,
            string optionsExtension,
            string optionsFilterName,
            string allFilterName,
            out string filename)
            where TDialog : FileDialog, new()
        {
            Contract.Requires(MessageHelper.MessageIsValid(title));
            Contract.Requires(MessageHelper.FileExtensionIsValid(optionsExtension));
            Contract.Requires(MessageHelper.MessageIsValid(optionsFilterName));
            Contract.Requires(MessageHelper.MessageIsValid(allFilterName));
            TDialog dialog = new TDialog();
            dialog.DefaultExt = optionsExtension;
            dialog.Title = title;
            dialog.Filter = HardwareOptionsHelper.GetFilterString(
                optionsExtension,
                optionsFilterName,
                allFilterName);
            bool result = dialog.ShowDialog() == true;
            filename = result ? dialog.FileName : null;
            return result;
        }

        #endregion

        #region ExportOptions Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateExportOptionsCommand(
            this MainViewModel viewModel,
            string displayName)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(p =>
                    {
                        string filename;
                        if (ShowOptionsFileDialog<SaveFileDialog>(
                            Resources.ExportOptions_Dialog_Title,
                            HardwareOptionsHelper.OptionsExtension,
                            Resources.Filter_Options,
                            Resources.Filter_All,
                            out filename))
                        {
                            viewModel.ExportOptions(filename);
                            viewModel.ShowMessage(
                                string.Format(
                                    Resources.ExportOptions_SuccessMessageTemplate,
                                    HardwareOptionsHelper.GetFileDisplayName(filename)),
                                caption: viewModel.DisplayName);
                        }
                    }));
        }

        #endregion

        #region ImportOptions Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateImportOptionsCommand(
            this MainViewModel viewModel,
            string displayName)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(
                    execute: p =>
                    {
                        string filename;
                        if (ShowOptionsFileDialog<OpenFileDialog>(
                            Resources.ImportOptions_Dialog_Title,
                            HardwareOptionsHelper.OptionsExtension,
                            Resources.Filter_Options,
                            Resources.Filter_All,
                            out filename))
                        {
                            viewModel.ImportOptions(filename);
                            viewModel.ShowMessage(
                                string.Format(
                                    Resources.ImportOptions_SuccessMessageTemplate,
                                    HardwareOptionsHelper.GetFileDisplayName(filename)),
                                caption: viewModel.DisplayName);
                        }
                    },
                    canExecute: p => viewModel.IsDisconnected));
        }

        #endregion

        #region About Command

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static CommandViewModel CreateAboutCommand(
            this MainViewModel viewModel,
            string displayName)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(displayName != null);
            Contract.Ensures(Contract.Result<CommandViewModel>() != null);
            string dialogTitle = string.Format(
                Resources.About_Dialog_TitleTemplate,
                viewModel.DisplayName);
            return new CommandViewModel(
                displayName,
                new DelegateCommand(p =>
                    ShowAboutDialog(viewModel, dialogTitle, ApplicationHelper.PluginsInfo)));
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Ликвидировать объекты перед потерей области")]
        public static void ShowAboutDialog(
            MainViewModel viewModel,
            string title,
            IEnumerable<HardwarePluginInfo> pluginsInfo)
        {
            Contract.Requires(viewModel != null);
            Contract.Requires(MessageHelper.MessageIsValid(title));
            Contract.Requires(pluginsInfo != null);
            AboutViewModel aboutViewModel = new AboutViewModel(
                pluginsInfo,
                Resources.About_Dialog_ProductVersionTemplate,
                Release.VersionTemplate);
            var window = viewModel.Window;
            if (window != null)
            {
                window.ModalDialog = new ModalDialogViewModel(
                    string.Empty,
                    title,
                    ModalDialogViewModel.DialogButtons.Ok,
                    ModalDialogViewModel.CreateCommands(
                        new Action[]
                        {
                            window.ModalDialogCancel
                        }))
                    {
                        DialogContent = aboutViewModel
                    };
                window.ModalDialog.Show();
            }
        }

        #endregion
    }
}
